﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Word = Microsoft.Office.Interop.Word;
using Microsoft.Office.Interop.Word;
using IsmsExplorer.Infrastructure.Helper;
using IsmsExplorer.Application.AssetRegister;
using IsmsExplorer.Application.Documents;

namespace IsmsExplorer.IsmsDataImport.Parser {
    internal class MsWordIsoBookmarkParser {
        // ***************************************** Data *****************************************
        public static readonly string LOG_FILE_NAME = "ISODocumentMap-{0}.txt";
        const string ISOREF_BOOKMARK = "ISOref";
        Dictionary<string, List<string>> _controlToDocsMap = new Dictionary<string, List<string>>();

        public Dictionary<string, List<string>> ControlToDocumentsMap {
            get { return _controlToDocsMap; }
        }
        // Source folder to look into for word documents
        public string InputFolder { get; set; }
        // Whether search is recursive or not - false by default.
        public bool Recursive { get; set; }

        public string LogFileName {
            get;
            private set;
        }

        // ***************************************** Cosntruction *****************************************


        // ***************************************** Operations *****************************************
        string GetCurrentDateTimeForFileName() {
            // DateTime will be formatted as: "2008-06-15 21:15:04Z"
            // Need to make it file-system-compatible.
            string s = DateTime.Now.ToString("u");
            s = s.Replace("-", "");
            s = s.Replace(":", "");
            s = s.Replace(" ", "");
            s = s.Replace("Z", "");
            // Now assign the datetime to the log file name.
            return s;
        }

        internal void Parse() {
            InitialiseAndValidateState();
            // The log to be written.
            StringBuilder sbLog = new StringBuilder();
            // Create Word object once here! (so it's not recreated for every fileName.
            Word.Application oWord = new Word.Application();

            try {
                // First fetch all relevant files (ending in ".doc" or ".docx").
                // We don't want any "leftover" files, e.g. temp files or the parts of a file being edited which start with "~"
                System.IO.SearchOption searchOption = (Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
                var files = Directory.GetFiles(InputFolder, "*.*", searchOption)
                            .Where(f => ((f.EndsWith(".doc", StringComparison.OrdinalIgnoreCase) ||
                                          f.EndsWith(".docx", StringComparison.OrdinalIgnoreCase))
                                          && (f.IndexOf('~') == -1)));

                foreach (string fileName in files) {
                    ParseBookmarksForFile(fileName, oWord, sbLog);
                }

            } catch (Exception ex) {
                sbLog.AppendLine().Append("ERROR: ").AppendLine(ex.Message);
                throw;

            } finally {
                try {
                    if (oWord != null) {
                        oWord.Quit();
                        oWord = null;
                    }
                } catch { 
                }
                WriteLogFile(sbLog, append: false);
            }

        }

        private void ParseBookmarksForFile(string fileName, Word.Application oWord, StringBuilder sbLog) {
            if (!File.Exists(fileName)) {
                throw new ArgumentException(string.Format("File {0} to validate bookmarks does not exist", fileName));
            }
            object missing = System.Reflection.Missing.Value;
            //object visible = false;
            object readOnly = true;
            object sourceFileName = fileName;
            // Say no so that we don't suddenly get presented with dialog to save (and disrupt parsing)
            object saveChanges = false;
            Word.Document oDoc = null;
            //StringBuilder sb = new StringBuilder("");
            string strBookmark = null;
            try {
                sbLog.AppendFormat("Attempting to open/parse file: {0}", fileName).AppendLine();
                oDoc = oWord.Documents.Open(ref sourceFileName, ref missing, ref readOnly);
                //sb.AppendLine();
                //strBookmark = GetBookmark(oDoc, "AuthorisedByName").Replace(",", "|");
                //sb.Append(strBookmark).Append(",");
                //strBookmark = GetBookmark(oDoc, "AuthorisedByTitle").Replace(",", "|");
                //sb.Append(strBookmark).Append(",");
                //strBookmark = GetBookmark(oDoc, "Classification").Replace(",", "|");
                //sb.Append(strBookmark).Append(",");
                strBookmark = GetBookmark(oDoc, ISOREF_BOOKMARK); //.Replace(",", "|");
                if (!string.IsNullOrEmpty(strBookmark)) {
                    sbLog.AppendFormat("Retrieved bookmark content: {0}", strBookmark).AppendLine();
                    sbLog.AppendLine("Attempting to map controls to document");
                    // Associate all referenced controls to this document.
                    string[] controls = strBookmark.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    foreach (string s in controls) {
                        if (Iso27001Helper.Validate(s)) {
                            MapDocumentToReferencedControls(s.Trim(), fileName);
                            sbLog.AppendFormat("Mapped control {0} to file {1}", s, fileName).AppendLine();

                        } else {
                            sbLog.AppendFormat("Invalid ISO 27001 control Ref ID found: {0}", s).AppendLine();
                        }
                    }

                } else {
                    sbLog.AppendFormat("FAILED to retrieve bookmark content for file").AppendLine();
                }

            } catch (Exception ex) {
                sbLog.AppendFormat("ERROR processing file: {0} - '{1}'", sourceFileName, ex.Message).AppendLine();

            } finally {
                if (oDoc != null) { oDoc.Close(ref saveChanges, ref missing, ref missing); }
            }
        }

        private void MapDocumentToReferencedControls(string refID, string fileName) {
            List<string> names = null;
            if (!_controlToDocsMap.TryGetValue(refID, out names)) {
                names = new List<string>();
                _controlToDocsMap.Add(refID, names);
            }
            names.Add(fileName);
        }

        private string GetBookmark(Word.Document oDoc, string bookmarkName) {
            string strBookmark = null;
            if (string.IsNullOrEmpty(bookmarkName)) {
                throw new ArgumentException("Bookmark name is empty");
            }
            if (oDoc.Bookmarks.Exists(bookmarkName)) {
                Bookmark bookmark = oDoc.Bookmarks[bookmarkName];
                string text = bookmark.Range.Text.Trim();
                if (!string.IsNullOrEmpty(text)) {
                    strBookmark = text;
                }
            }
            return strBookmark;
        }

        private void WriteLogFile(StringBuilder sbLog, bool append) {
            using (TextWriter tw = new StreamWriter(LogFileName, append)) {
                tw.Write(sbLog.ToString());
            }
        }

        private void InitialiseAndValidateState() {
            if (!Directory.Exists(InputFolder)) {
                throw new ArgumentException("Input (root) folder does not exist");
            }
            string logFileNameOnly = string.Format(LOG_FILE_NAME, GetCurrentDateTimeForFileName());
            this.LogFileName = string.Concat(this.InputFolder, Path.DirectorySeparatorChar.ToString(), logFileNameOnly);
            // Clear control to docs cache.
            _controlToDocsMap.Clear();
        }

    }
}
